﻿using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Badger.Vhdl;
using Weazel.Badger.Vhdl.Types;
using Weazel.Badger.Vhdl.Statements;
using Weazel.Badger.Vhdl.Expressions;

namespace Weazel.Willows.InterfaceGeneration
{
	public class SoftwareToHardwareQueue
		: HardwareQueue
	{
		/// <summary>
		/// Get the (expected) name of the 'do_read' port on the hardware entity
		/// </summary>
		public string HardwareDoReadPortName
		{
		  get { return "out_queue_" + Queue.Readers[0].LocalName + "_do_read"; }
		}

		/// <summary>
		/// Get the (expected) name of the 'can_read' port on the hardware entity
		/// </summary>
		public string HardwareCanReadPortName
		{
			get { return "queue_" + Queue.Readers[0].LocalName + "_can_read"; }
		}

		/// <summary>
		/// Get the (expected) name of the 'data_out' port on the hardware entity
		/// </summary>
		public string HardwareDataOutPortName
		{
			get { return "queue_" + Queue.Readers[0].LocalName + "_port"; }
		}

		private List<Weazel.Badger.Vhdl.Signal> writingEndDataSignals;
		public List<Weazel.Badger.Vhdl.Signal> WritingEndDataSignals
		{
			get { return writingEndDataSignals; }
		}

		public override void InsertIntoUserLogicEntity(UserLogicEntity entity)
		{
			// call base method to insert common signals
			base.InsertIntoUserLogicEntity(entity);

			// insert the "segmented" writing end signals
			foreach (Weazel.Badger.Vhdl.Signal signal in writingEndDataSignals)
			{
				entity.AddDeclarativeItem(signal);
				entity.AddSignal(signal);
			}
		}

		private void initializeSignals()
		{
			System.Diagnostics.Trace.Assert(Queue != null);

			writingEndDataSignals = new List<Weazel.Badger.Vhdl.Signal>();
			for (int i = 0; i < AddressesNeeded; i++)
			{
				Weazel.Badger.Vhdl.Signal signal =
					new Weazel.Badger.Vhdl.Signal(new StdLogicVector(32), null);

				writingEndDataSignals.Add(signal);
			}
		}

		public SoftwareToHardwareQueue(MemoryMap memoryMap, HardwareQueueRegistry queueRegistry, Weazel.Willows.Model.Queue queue)
			: base(memoryMap, queueRegistry, queue)
		{
			initializeSignals();
		}

		private SignalAssignmentStatement getAssignPartialSignalExpression(
			StatementCollection collection, Signal result, Signal value)
		{
			System.Diagnostics.Trace.Assert(result.Type.TypeId == Types.StdLogicVector);
			System.Diagnostics.Trace.Assert(value.Type.TypeId == Types.StdLogicVector);

			if (result.Type.Width == value.Type.Width)
			{
				// simply connect the two
				return new SignalAssignmentStatement(
					result,
					new SignalReferenceExpression(
						collection,
						value
					)
				);
			}
			else if (result.Type.Width > value.Type.Width)
			{
				return new SignalAssignmentStatement(
					result,
					new SignalReferenceExpression(
						collection,
						value),
					0,
					value.Type.Width - 1,
					true
				);
			}
			else
			{
				// width of value must be < width of result
				ArrayIndexExpression rangeExpression =
					new ArrayIndexExpression(
						collection,
						new SignalReferenceExpression(
							collection,
							value
						),
						new ConstantExpression(0),
						new ConstantExpression(result.Type.Width - 1),
						false,
						null,
						null
					);

				return new SignalAssignmentStatement(
					result,
					rangeExpression
				);
			}
		}

		public override void InsertQueue(UserLogicEntity entity)
		{
			base.InsertQueue(entity);

			if (AddressesNeeded > 1)
			{
				throw new NotImplementedException();
			}
			else
			{
				// simply connect the data out signal of the queue
				// with the one 'segmented' signal  

				SignalAssignmentStatement statement =
					getAssignPartialSignalExpression(
						entity.Statements,						
						WritingEndDataSignal,
						writingEndDataSignals[0]
					);


				//new SignalAssignmentStatement(
				//  readingEndDataSignals[0],
				//  new SignalReferenceExpression(entity.Statements, ReadingEndDataSignal)
				//);

				entity.Statements.Add(statement);
			}
		}

		public override void SetupReadProcessCase(UserLogicEntity entity)
		{
			// Software to hardware queues cannot be read.
			// return the value "deadbeef" if read anyways
			for (int i = 0; i < AddressesNeeded; i++)
			{
				Expression choiceExpression =
					GetChoiceExpression(
					entity.ReadProcessStatementCollection,
					GetRelativeAddressAsPositionalString(RelativeBaseAddress + i)
				);

				CaseStatementChoice choice =
					new CaseStatementChoice(choiceExpression);

				// a software to hardware connection cannot be read..
				SignalAssignmentStatement stmt =
					new SignalAssignmentStatement(
						entity.Ip2BusDataSignal,
						new ConstantExpression(
							choice.Statements,
							"X\"DEADBEEF\"",
							new StdLogicVector(32),
							null
						)
					);

				choice.Statements.Add(stmt);
				entity.ReadProcessCaseStatement.AddChoice(choice);
			}
		}

		public override void SetupWriteProcessCase(UserLogicEntity entity)
		{
			// The bus has a limited capacity of 32 bits. Weazel values on the 
			// other hand are not restricted in size. If the value of the queue is
			// greater than 32 bits it must be written in chunks of (atmost) 32 bits. 
			// If this is the case we need to add extra registers to sample the entire
			// value before writing it to the actual queue (fifo) ..

			if (AddressesNeeded == 1)
			{
				 // the simple case. 
				Expression choiceExpression = 
					GetChoiceExpression(
						entity.WriteProcessOnClockStatementCollection, 
						GetRelativeAddressAsPositionalString(RelativeBaseAddress)
					);

				CaseStatementChoice choice = 
					new CaseStatementChoice(choiceExpression);

				// one 32-bit signal only
				Weazel.Badger.Vhdl.Signal signalToWrite = 
					writingEndDataSignals[0];

				SignalAssignmentStatement assignDataSignal =
					new SignalAssignmentStatement(
					 signalToWrite,
					 new SignalReferenceExpression(
						entity.WriteProcessOnClockStatementCollection,
						entity.Bus2IpDataPort
					 )
				 );

				 choice.Statements.Add(new Comment(string.Format("write queue '{0}'", Queue.Name)));
				 choice.Statements.Add(assignDataSignal);

				 entity.WriteProcessCaseStatement.AddChoice(choice);

				// set the do write signal

				 Weazel.Badger.Vhdl.Expressions.ConstantExpression one =
					 new Weazel.Badger.Vhdl.Expressions.ConstantExpression(
					 choice.Statements,
					 "1",
					 new StdLogic(),
					 null
				 );

				 SignalAssignmentStatement assignOneToDoRead =
					 new SignalAssignmentStatement(
						 WritingEndDoWriteSignal,
						 one
					 );

				 choice.Statements.Add(assignOneToDoRead);

				 // also insert a clear statement for the do read signal
				 // BEFORE the case statement (at the top that is)
				 ConstantExpression zero =
					 new ConstantExpression(
						 choice.Statements,
						 "'0'",
						 new StdLogic(),
						 null
					 );

				 SignalAssignmentStatement assignZeroToDoRead =
					 new SignalAssignmentStatement(
						 WritingEndDoWriteSignal,
						 zero
					 );

				 entity.WriteProcessOnClockStatementCollection.InsertFirst(assignZeroToDoRead);
			}
			else
			{
				for (int i = 0; i < AddressesNeeded; i++)
				{
					// the simple case. 
					Expression choiceExpression =
						GetChoiceExpression(
							entity.WriteProcessOnClockStatementCollection,
							GetRelativeAddressAsPositionalString(RelativeBaseAddress + i)
						);

					CaseStatementChoice choice =
						new CaseStatementChoice(choiceExpression);

					throw new NotImplementedException("missing something here");

					// if this is the last addresses in the range 
			    // of the connection write a 1 to the "do_write"
			    // signal of the queue
					if (i == AddressesNeeded - 1)
					{
						SignalAssignmentStatement assignOneDoWrite =
							GetAssignOneToDoWrite(choice.Statements);

						choice.Statements.Add(assignOneDoWrite);

						// also insert a zero assignment of the do write
						// signal before the case statemetn	
						SignalAssignmentStatement assignZeroDoWrite =
							GetAssignZeroToDoWrite(entity.WriteProcessOnClockStatementCollection);

						entity.WriteProcessOnClockStatementCollection.InsertFirst(assignZeroDoWrite);
					}
				}
			}			
		}
	} // end class
} // end namespace
